/*
 * kallsyms.c: in-kernel printing of symbolic oopses and stack traces.
 *
 * Rewritten and vastly simplified by Rusty Russell for in-kernel
 * module loader:
 *   Copyright 2002 Rusty Russell <rusty@rustcorp.com.au> IBM Corporation
 *
 * ChangeLog:
 *
 * (25/Aug/2004) Paulo Marques <pmarques@grupopie.com>
 *      Changed the compression method from stem compression to "table lookup"
 *      compression (see scripts/kallsyms.c for a more complete description)
 */
#include <seminix/kallsyms.h>
#include <seminix/init.h>
#include <seminix/err.h>
#include <seminix/sched.h>
#include <seminix/ctype.h>
#include <seminix/slab.h>
#include <seminix/types.h>

/*
 * These will be re-linked against their real values
 * during the second link stage.
 */
extern const unsigned long kallsyms_addresses[] __weak;
extern const int kallsyms_offsets[] __weak;
extern const u8 kallsyms_names[] __weak;

/*
 * Tell the compiler that the count isn't in the small data section if the arch
 * has one (eg: FRV).
 */
extern const unsigned int kallsyms_num_syms
__attribute__((weak, section(".rodata")));

extern const unsigned long kallsyms_relative_base
__attribute__((weak, section(".rodata")));

extern const u8 kallsyms_token_table[] __weak;
extern const u16 kallsyms_token_index[] __weak;

extern const unsigned int kallsyms_markers[] __weak;

/*
 * Expand a compressed symbol data into the resulting uncompressed string,
 * if uncompressed string is too long (>= maxlen), it will be truncated,
 * given the offset to where the symbol is in the compressed stream.
 */
static unsigned int kallsyms_expand_symbol(unsigned int off,
                       char *result, usize maxlen)
{
    int len, skipped_first = 0;
    const u8 *tptr, *data;

    /* Get the compressed symbol length from the first symbol byte. */
    data = &kallsyms_names[off];
    len = *data;
    data++;

    /*
     * Update the offset to return the offset for the next symbol on
     * the compressed stream.
     */
    off += len + 1;

    /*
     * For every byte on the compressed symbol data, copy the table
     * entry for that byte.
     */
    while (len) {
        tptr = &kallsyms_token_table[kallsyms_token_index[*data]];
        data++;
        len--;

        while (*tptr) {
            if (skipped_first) {
                if (maxlen <= 1)
                    goto tail;
                *result = *tptr;
                result++;
                maxlen--;
            } else
                skipped_first = 1;
            tptr++;
        }
    }

tail:
    if (maxlen)
        *result = '\0';

    /* Return to offset to the next symbol. */
    return off;
}

/*
 * Find the offset on the compressed stream given and index in the
 * kallsyms array.
 */
static unsigned int get_symbol_offset(unsigned long pos)
{
    const u8 *name;
    int i;

    /*
     * Use the closest marker we have. We have markers every 256 positions,
     * so that should be close enough.
     */
    name = &kallsyms_names[kallsyms_markers[pos >> 8]];

    /*
     * Sequentially scan all the symbols up to the point we're searching
     * for. Every symbol is stored in a [<len>][<len> bytes of data] format,
     * so we just need to add the len to the current pointer for every
     * symbol we wish to skip.
     */
    for (i = 0; i < (pos & 0xFF); i++)
        name = name + (*name) + 1;

    return name - kallsyms_names;
}

static unsigned long kallsyms_sym_address(int idx)
{
    if (!IS_ENABLED(CONFIG_KALLSYMS_BASE_RELATIVE))
        return kallsyms_addresses[idx];

    /* values are unsigned offsets if --absolute-percpu is not in effect */
    if (!IS_ENABLED(CONFIG_KALLSYMS_ABSOLUTE_PERCPU))
        return kallsyms_relative_base + (u32)kallsyms_offsets[idx];

    /* ...otherwise, positive offsets are absolute values */
    if (kallsyms_offsets[idx] >= 0)
        return kallsyms_offsets[idx];

    /* ...and negative offsets are relative to kallsyms_relative_base - 1 */
    return kallsyms_relative_base - 1 - kallsyms_offsets[idx];
}

/* Lookup the address for this symbol. Returns 0 if not found. */
unsigned long kallsyms_lookup_name(const char *name)
{
    char namebuf[KSYM_NAME_LEN];
    unsigned long i;
    unsigned int off;

    for (i = 0, off = 0; i < kallsyms_num_syms; i++) {
        off = kallsyms_expand_symbol(off, namebuf, ARRAY_SIZE(namebuf));

        if (strcmp(namebuf, name) == 0)
            return kallsyms_sym_address(i);
    }
    return 0;
}

int kallsyms_on_each_symbol(int (*fn)(void *, const char *, unsigned long),
                void *data)
{
    char namebuf[KSYM_NAME_LEN];
    unsigned long i;
    unsigned int off;
    int ret;

    for (i = 0, off = 0; i < kallsyms_num_syms; i++) {
        off = kallsyms_expand_symbol(off, namebuf, ARRAY_SIZE(namebuf));
        ret = fn(data, namebuf, kallsyms_sym_address(i));
        if (ret != 0)
            return ret;
    }
    return 0;
}

static unsigned long get_symbol_pos(unsigned long addr,
                    unsigned long *symbolsize,
                    unsigned long *offset)
{
    unsigned long symbol_start = 0, symbol_end = 0;
    unsigned long i, low, high, mid;

    /* This kernel should never had been booted. */
    if (!IS_ENABLED(CONFIG_KALLSYMS_BASE_RELATIVE))
        BUG_ON(!kallsyms_addresses);
    else
        BUG_ON(!kallsyms_offsets);

    /* Do a binary search on the sorted kallsyms_addresses array. */
    low = 0;
    high = kallsyms_num_syms;

    while (high - low > 1) {
        mid = low + (high - low) / 2;
        if (kallsyms_sym_address(mid) <= addr)
            low = mid;
        else
            high = mid;
    }

    /*
     * Search for the first aliased symbol. Aliased
     * symbols are symbols with the same address.
     */
    while (low && kallsyms_sym_address(low-1) == kallsyms_sym_address(low))
        --low;

    symbol_start = kallsyms_sym_address(low);

    /* Search for next non-aliased symbol. */
    for (i = low + 1; i < kallsyms_num_syms; i++) {
        if (kallsyms_sym_address(i) > symbol_start) {
            symbol_end = kallsyms_sym_address(i);
            break;
        }
    }

    /* If we found no next symbol, we use the end of the section. */
    if (!symbol_end) {
        if (is_kernel_inittext(addr))
            symbol_end = (unsigned long)_einittext;
        else if (IS_ENABLED(CONFIG_KALLSYMS_ALL))
            symbol_end = (unsigned long)_end;
        else
            symbol_end = (unsigned long)_etext;
    }

    if (symbolsize)
        *symbolsize = symbol_end - symbol_start;
    if (offset)
        *offset = addr - symbol_start;

    return low;
}

/*
 * Lookup an address but don't bother to find any names.
 */
int kallsyms_lookup_size_offset(unsigned long addr, unsigned long *symbolsize,
                unsigned long *offset)
{
    if (is_ksym_addr(addr))
        return !!get_symbol_pos(addr, symbolsize, offset);
    return 0;
}

/*
 * Lookup an address
 * - modname is set to NULL if it's in the kernel.
 * - We guarantee that the returned name is valid until we reschedule even if.
 *   It resides in a module.
 * - We also guarantee that modname will be valid until rescheduled.
 */
const char *kallsyms_lookup(unsigned long addr,
                unsigned long *symbolsize,
                unsigned long *offset,
                char **modname, char *namebuf)
{
    namebuf[KSYM_NAME_LEN - 1] = 0;
    namebuf[0] = 0;

    if (is_ksym_addr(addr)) {
        unsigned long pos;

        pos = get_symbol_pos(addr, symbolsize, offset);
        /* Grab name */
        kallsyms_expand_symbol(get_symbol_offset(pos),
                       namebuf, KSYM_NAME_LEN);
        if (modname)
            *modname = NULL;
        return namebuf;
    }

    return NULL;
}

int lookup_symbol_name(unsigned long addr, char *symname)
{
    symname[0] = '\0';
    symname[KSYM_NAME_LEN - 1] = '\0';

    if (is_ksym_addr(addr)) {
        unsigned long pos;

        pos = get_symbol_pos(addr, NULL, NULL);
        /* Grab name */
        kallsyms_expand_symbol(get_symbol_offset(pos),
                       symname, KSYM_NAME_LEN);
        return 0;
    }
    /* See if it's in a module. */
    return -ERANGE;
}

int lookup_symbol_attrs(unsigned long addr, unsigned long *size,
            unsigned long *offset, char *modname, char *name)
{
    name[0] = '\0';
    name[KSYM_NAME_LEN - 1] = '\0';

    if (is_ksym_addr(addr)) {
        unsigned long pos;

        pos = get_symbol_pos(addr, size, offset);
        /* Grab name */
        kallsyms_expand_symbol(get_symbol_offset(pos),
                       name, KSYM_NAME_LEN);
        modname[0] = '\0';
        return 0;
    }
    /* See if it's in a module. */
    return -ERANGE;
}

/* Look up a kernel symbol and return it in a text buffer. */
static int __sprint_symbol(char *buffer, unsigned long address,
               int symbol_offset, int add_offset)
{
    char *modname;
    const char *name;
    unsigned long offset, size;
    int len;

    address += symbol_offset;
    name = kallsyms_lookup(address, &size, &offset, &modname, buffer);
    if (!name)
        return sprintf(buffer, "0x%lx", address - symbol_offset);

    if (name != buffer)
        strcpy(buffer, name);
    len = strlen(buffer);
    offset -= symbol_offset;

    if (add_offset)
        len += sprintf(buffer + len, "+%#lx/%#lx", offset, size);

    if (modname)
        len += sprintf(buffer + len, " [%s]", modname);

    return len;
}

/**
 * sprint_symbol - Look up a kernel symbol and return it in a text buffer
 * @buffer: buffer to be stored
 * @address: address to lookup
 *
 * This function looks up a kernel symbol with @address and stores its name,
 * offset, size and module name to @buffer if possible. If no symbol was found,
 * just saves its @address as is.
 *
 * This function returns the number of bytes stored in @buffer.
 */
int sprint_symbol(char *buffer, unsigned long address)
{
    return __sprint_symbol(buffer, address, 0, 1);
}

/**
 * sprint_symbol_no_offset - Look up a kernel symbol and return it in a text buffer
 * @buffer: buffer to be stored
 * @address: address to lookup
 *
 * This function looks up a kernel symbol with @address and stores its name
 * and module name to @buffer if possible. If no symbol was found, just saves
 * its @address as is.
 *
 * This function returns the number of bytes stored in @buffer.
 */
int sprint_symbol_no_offset(char *buffer, unsigned long address)
{
    return __sprint_symbol(buffer, address, 0, 0);
}

/**
 * sprint_backtrace - Look up a backtrace symbol and return it in a text buffer
 * @buffer: buffer to be stored
 * @address: address to lookup
 *
 * This function is for stack backtrace and does the same thing as
 * sprint_symbol() but with modified/decreased @address. If there is a
 * tail-call to the function marked "noreturn", gcc optimized out code after
 * the call so that the stack-saved return address could point outside of the
 * caller. This function ensures that kallsyms will find the original caller
 * by decreasing @address.
 *
 * This function returns the number of bytes stored in @buffer.
 */
int sprint_backtrace(char *buffer, unsigned long address)
{
    return __sprint_symbol(buffer, address, -1, 1);
}
